﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Antlr.Runtime;
using CommandLine;
using CommandLine.Text;

namespace VSDoc2JsDoc
{
    class Program
    {
        class Options : CommandLineOptionsBase
        {
            [Option("p", "projectDir", Required = true)]
            public string ProjectDir;

            [Option("i", "index", Required = true)]
            public string JsIndexPath;

            [Option("o", "output", Required = true)]
            public string OutputDir;

            [Option("c", "config", Required = true)]
            public string JsDocConfig;
        }

        class FilePair
        {
            public string Input { get; set; }
            public string Output { get; set; }

            public StreamWriter EnsureFile()
            {
                List<string> dirs = new List<string>();
                for (string dir = Path.GetDirectoryName(Output); !Directory.Exists(dir); dir = Path.GetDirectoryName(dir))
                {
                    dirs.Add(dir);
                }

                dirs.Reverse();
                foreach (var dir in dirs)
                {
                    Directory.CreateDirectory(dir);
                }

                return File.CreateText(Output);
            }
        }

        private static Options options;
        private static JsParser parser = new JsParser();
        private static VsDocTransform transform = new VsDocTransform();

        private static List<FilePair> files = new List<FilePair>();
        private static List<string> namespaces = new List<string>();

        static int Main(string[] args)
        {
            options = new Options();
            ICommandLineParser commandLineParser = new CommandLineParser(new CommandLineParserSettings(Console.Error));
            if (!commandLineParser.ParseArguments(args, options))
            {
                return 1; 
            }
        
            if (!File.Exists(options.JsIndexPath))
            {
                Console.Error.WriteLine("index file does not exitst!");
                return 2;
            }

            if (!Directory.Exists(options.ProjectDir))
            {
                Console.Error.WriteLine("Project directory does not exitst!");
                return 3;
            }

            if (!Directory.Exists(options.OutputDir))
            {
                Directory.CreateDirectory(options.OutputDir);
            }

            GetFileNames();

            int count = 1;
            foreach (FilePair pair in files)
            {
#if DEBUG
                //Console.WriteLine("Processing [{0}/{1}]: {2}", count, files.Count, pair.Input);
#endif
                try
                {
                
                    string fileContents = File.ReadAllText(pair.Input);

                    JsScript script = parser.Parse(fileContents);

                    string output = transform.Apply(fileContents, script);
                    using (StreamWriter writer = pair.EnsureFile())
                    {
                        writer.Write(output);
                    }

                    GetNamespaces(script.ClassDefinitions);

                    count++;
                }
                catch
                {
                    Console.Error.WriteLine("Error while processing file: {0}", pair.Input);
                    throw;
                }
            }

            string namespacesJs = Path.Combine(options.OutputDir, "__namespaces.js");
            WriteNamespaces(namespacesJs);
            GenerateJsDocConfig(Enumerable.Repeat(namespacesJs, 1).Concat(files.Select(p => p.Output)));

            return 0;
        }

        static void GetFileNames()
        {
            System.Xml.XmlDocument xDoc = new System.Xml.XmlDocument();
            xDoc.Load(options.JsIndexPath);
            var fileNodes = xDoc.DocumentElement.SelectNodes("//File[not(exclude/@name='vsdoc2jsdoc')]");
            foreach (System.Xml.XmlNode fileNode in fileNodes)
            {
                string file = "";
                string output = "";
                try
                {
                    file = Path.Combine(options.ProjectDir, fileNode.Attributes["path"].Value);
                    output = Path.Combine(options.OutputDir, fileNode.Attributes["path"].Value);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(options.ProjectDir + "   " + fileNode.Attributes["path"].Value);
                    throw ex;
                }
                if (!File.Exists(file))
                {
                    Console.Error.WriteLine("The specified file {0} does not exists! Referenced at {1}:{2}", file, options.JsIndexPath, fileNode.Attributes["path"].Value);
                    continue;
                }

                FilePair pair = new FilePair()
                {
                    Input = file,
                    Output = output
                };

                files.Add(pair);
            }
        }

        static void GenerateJsDocConfig(IEnumerable<String> files)
        {
            string content = String.Format(@"
{{
    _: [
        {0}],
    p: false,
    a: false
}}
", String.Join("," + Environment.NewLine + "        ", files.Select(p => Path.GetFullPath(p).Replace('\\', '/')).Select(o => "\"" + o + "\"")));

            File.WriteAllText(options.JsDocConfig, content);
        }


        static void GetNamespaces(List<ClassDefinition> classDefinitions)
        {
            foreach (ClassDefinition classDef in classDefinitions)
            {
                int cindex = classDef.ClassName.LastIndexOf('.');

                if (cindex > 0)
                {
                    string parentNs = classDef.ClassName.Substring(0, cindex);
                    string[] parts = parentNs.Split('.');

                    for (int i = 0; i < parts.Length; i++)
                    {
                        string ns = String.Join(".", parts, 0, i + 1);

                        if (!namespaces.Contains(ns))
                        {
                            namespaces.Add(ns);
                        }
                    }
                }
            }
        }

        static void WriteNamespaces(string output)
        {
            StringBuilder nsout = new StringBuilder();
            foreach (string ns in namespaces.OrderBy(n => n))
            {
                nsout.AppendLine("/**");
                nsout.AppendLine("    @name " + ns);
                nsout.AppendLine("    @namespace");
                nsout.AppendLine("*/");
                nsout.AppendLine();
                nsout.AppendLine();
            }

            File.WriteAllText(output, nsout.ToString());
        }
    }
}
